#pragma once

#include<iostream>
#include<sys/types.h>
#include<vector>
#include<unordered_map>
#include<algorithm>
#include<sstream>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/sendfile.h>
#include<sys/stat.h>
#include"Log.hpp"
#include"Util.hpp"


#define SPACES " "
#define SEP ": "
#define LINE_END "\r\n"
#define WWWROOT "wwwroot" 
#define HOME_PATH "index.html"
#define VERSION "HTTP/1.0"
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"


#define OK 200
#define NOT_FOND 404
#define SERVER_ERR 500

static std::string Code2Desc(int code)
{
  std::string desc;
  switch(code){
    case 200:
      desc = "Ok";
      break;
    case 404:
      desc = "Not Fond";
      break;
    case 500:
      desc = "Internal Server Error";
      break;
    default:
      break;
  }
  return desc;
}

static std::string Suffix2Desc(const std::string &suffix)
{
  std::string desc("text/html");
  std::unordered_map<std::string,std::string> suffix2desc{
    {".html", "text/html"},
    {".css", "text/css"},
    {".js", "application/javascript"},
    {".jpg", "application/x-jpg"},
    {".xml", "application/xml"},
  };
  auto it = suffix2desc.find(suffix);
  if(it != suffix2desc.end())
  {
    desc = it->second;
  }
  return desc;
}

class HttpRequest{
  public:
    std::string request_line;
    std::vector<std::string> request_header;
    std::string request_body;

    //解析完的结果
    std::string method;
    std::string uri;
    std::string version;


    std::unordered_map<std::string,std::string> header_kv; 
    int content_length;//接收完body后初始化可用
    bool cgi;

    std::string uri_path;
    std::string uri_message;
    std::string file_suffix;


    HttpRequest():content_length(0),cgi(false)
    {}
    ~HttpRequest()
    {}
};
class HttpResponse{
  public:
    std::string respons_line;
    std::vector<std::string> respons_header;
    std::string blank = LINE_END;
    std::string respons_body;

    
    int code;
    int fd;
    int content_length;
    HttpResponse():code(OK),fd(-1),content_length(0)
    {}
    ~HttpResponse()
    {}

};
class EndPoint{
  private:
  HttpRequest http_request;
  HttpResponse http_response;
  int _sock;
  bool stop;//判断读取是否停止，防止对端关闭连接

  private:
  bool RecvHttpRequestLine()
  {
    auto &request_line = http_request.request_line;
    if(Util::GetHttpLine(_sock,request_line) > 0)
    {
      //去掉"\n"
      request_line.resize(request_line.size()-1);
      Log::LOG(INFO,request_line.c_str());
      return stop;
    }
    else {
      Log::LOG(WARNING,"RecvHttpRequestLine error");
      stop = true;
      return stop;
    }
  }
  bool RecvHttpRequestHeader()
  {
    std::string line = "x";
    auto& request_header = http_request.request_header;
    Log::LOG(INFO,"RecvHttpRequestHeader begin");
    while(true)
    {
      line.clear();
      if(Util::GetHttpLine(_sock,line) <= 0)
      {
        Log::LOG(WARNING,"RecvHttpRequestHeader error");
        stop = true;
        return stop;
      }
      if(line == "\n")
        break;
      //去掉"\n"
      line.resize(line.size()-1);
      Log::LOG(INFO,line);
      request_header.push_back(line);
    }
    Log::LOG(INFO,"RecvHttpRequestHeader end");
    return stop;
  }
  void RecvHttpRequestBody()
  {
    if(http_request.method == "POST")
    {
      auto it = http_request.header_kv.find("Content-Length");
      if(it == http_request.header_kv.end())
      {
        Log::LOG(ERROR,"POST not foun Content-Length");
        return;
      }
      http_request.content_length = atoi((it->second).c_str());

      char ch;
      for(int i = 0;i < http_request.content_length;i++)
      {
        if(recv(_sock,&ch,1,0) < 0)
        {
          Log::LOG(ERROR,"recv request_body error");
          return;
        }
        http_request.request_body += ch;
      }
      Log::LOG(DEBUG,"request_body:"+http_request.request_body);
    }
  }
  void ParseHttpRequestLine()
  {
    const auto &request_line = http_request.request_line;
    auto &method =  http_request.method;
    auto &uri =  http_request.uri;
    auto &version =  http_request.version;


    //method /xxx?xxx version
    std::stringstream ss(request_line);
    ss >> method >> uri >> version;

    //统一把方法转为全大写
    std::transform(method.begin(),method.end(),method.begin(),::toupper);

  }
  void ParseHttpRequestHeader()
  {
    Log::LOG(INFO,"ParseHttpRequestHeader begin");
     auto &header_kv = http_request.header_kv;
    for(auto it : http_request.request_header)
    {
      std::string left,right;
      Util::CutString(left,right,SEP,it);
      Log::LOG(DEBUG,left+"["+right+"]");
      header_kv[left] = right;
    }
    Log::LOG(INFO,"ParseHttpRequestHeader end");

  }

    //浏览器通过HttpServer与CGI程序进行信息交换。
    //HttpServer与CGI程序通过管道进行交互。
    //从而实现浏览器与CGI程序的交互。
    int HandlerRespondCgi()
    {
      //可执行程序路径在path
      auto &method = http_request.method;
      auto &uri_path = http_request.uri_path;
      auto &uri_message = http_request.uri_message;
      auto &request_body = http_request.request_body;
      //服务器不能挂，用frok创建子进程进行替换。
      //exec系列函数进行程序替换
      //程序替换后，子进程不认识上面变量。
      //需要父进程通过进程间通信，将信息交给子进程。
      //环境变量不会随程序替换而消失，因而简短的信息可以通过环境变量来传替。
      //setenv()添加环境变量，getenv()获取环境变量的值。环境变量是kv结构的。[VALUE=value]

      std::string conlen = http_request.header_kv["Content-Length"];
      Log::LOG(DEBUG,"request > POST Content-Length: "+ conlen);
      setenv("METHOD",method.c_str(),1);
      setenv("CONTENT_LENGTH",conlen.c_str(),1);

      if(method == "GET")
      {
        setenv("MESSAGE",uri_message.c_str(),1);
      }
      

      //使用pipe创建匿名管道。
      //匿名管道为单向的，所以要创建两个管道
      //管道按照父进程的视角看
      //outpipe[1]为写入管道，inpipe[0]为读取管道
      int outpipe[2];
      int inpipe[2];
      pipe(outpipe);
      pipe(inpipe);

     pid_t pid = fork(); 
     if(pid < 0)
     {
        Log::LOG(ERROR,"fork is error");
        return SERVER_ERR;
     }
     else if(pid == 0)
     {
       //子进程
       close(outpipe[1]);
       close(inpipe[0]);

       Log::LOG(INFO,"CGI调用，path:" + uri_path);
       //进程替换后，子进程无法获取管道的文件描述符，但是对应的文件资源没有关闭
       //我们可以将管道重定向到标准输入输出流中，就能通过标准输入输出的文件描述符找回管道。
       //使用dup2(int oldfd,int newfd)函数。-> 将oldfd拷贝到newfd上
       dup2(outpipe[0],0);
       dup2(inpipe[1],1);
       
        if(execl(uri_path.c_str(),uri_path.c_str(),nullptr) == -1)
        {
          std::cerr << "execl fail" << std::endl;
        }
       exit(1);
     }
     else
     {
      //父进程
      close(outpipe[0]);
      close(inpipe[1]);

      //向子进程传输信息
      //可能消息过多管道,不能只写入1次
      char* str = (char*)request_body.c_str();
      int begin = 0;//设置开始读取位置
      int remain = request_body.size();//剩余未读
        while(remain > 0)
        {
          int wtsz = write(outpipe[1],(void*)(str+begin),remain);
          remain -= wtsz;
          begin += wtsz;
        }


        char ch;
        int rdsz = 1; 
        while((rdsz = read(inpipe[0],(void*)&ch,1)) > 0)
        {
          if(rdsz < 0)
          {
            Log::LOG(ERROR,"read cgi message error");
            return SERVER_ERR;
          }
          http_response.respons_body += ch;
        }
        http_response.content_length = http_response.respons_body.size();
        Log::LOG(DEBUG,"CGI 返回数据："+http_response.respons_body);
     
        int err_code;
        int ret = waitpid(pid,&err_code,0);
        if(ret != pid)
        {
          Log::LOG(ERROR,"waitpid error");
          return SERVER_ERR;
        }
        if((!WIFEXITED(err_code)) && (WEXITSTATUS(err_code) != 0))
        {
          Log::LOG(ERROR,"child exit error");
          return SERVER_ERR;
        }

      close(outpipe[1]);
      close(inpipe[0]);
     }
     return OK;
    }

    int HandlerRespondNonCgi()
    {
      auto &uri_path = http_request.uri_path;
      http_response.fd = open(uri_path.c_str(),O_RDONLY);
      if(http_response.fd < 0)
      {
        Log::LOG(ERROR,"open " + uri_path + "error");
        return 500;
      }
      return OK;
    }
    void BuildHttpRespondOKHeader()
    {
      //构建响应header
      Log::LOG(INFO,"BuildHttpRespond begin");
      auto &file_suffix = http_request.file_suffix; 
      auto &respons_header = http_response.respons_header;
      std::string content_type;
      std::string content_length;

      content_type = "Content-Type";
      content_type += SEP;
      content_type += Suffix2Desc(file_suffix);
      Log::LOG(DEBUG,"respons_header:"+content_type);
      content_type += LINE_END;
      respons_header.push_back(content_type);
      content_length = "Content-Length";
      content_length += SEP;
      content_length += std::to_string(http_response.content_length);
      Log::LOG(DEBUG,"respons_header:"+content_length);
      content_length += LINE_END;
      respons_header.push_back(content_length);

      http_response.blank = LINE_END;
      Log::LOG(INFO,"BuildHttpRespond end");
    }
    void BuildHttpRespondERRHeader(const std::string& errpath)
    {
      Log::LOG(INFO,"BuildHttpERRRespond begin");
      //如果调用cgi但是出错，后面发送也按照非cgi发送
      http_request.cgi = false;

      std::string respons_path = WWWROOT; 
      respons_path += "/";
      respons_path += errpath;
      //打开错误文件读取
      http_response.fd = open(respons_path.c_str(),O_RDONLY);
      if(http_response.fd < -1)
      {
        Log::LOG(FATAL,"错误处理出错，open errhtml error");
      }
      Log::LOG(WARNING,"errpath in " + respons_path);
      
      struct stat buff;
      stat(respons_path.c_str(),&buff); 
      //重新写入respons的Content-Length
      http_response.content_length = buff.st_size;
      auto &respons_header = http_response.respons_header;
      std::string content_type;
      std::string content_length;

      content_type = "Content-Type";
      content_type += SEP;
      content_type += "text/html";
      Log::LOG(DEBUG,content_type);
      content_type += LINE_END;
      respons_header.push_back(content_type);
      content_length = "Content-Length";
      content_length += SEP;
      content_length += std::to_string(buff.st_size);
      Log::LOG(DEBUG,content_length);
      content_length += LINE_END;
      respons_header.push_back(content_length);

      http_response.blank = LINE_END;
      Log::LOG(INFO,"BuildHttpERRRespond end");
    }
    void BuildHttpRespondHead()
    {
      //构建响应第一行
      auto &respons_line = http_response.respons_line;
      respons_line += VERSION;
      respons_line += " ";
      respons_line += std::to_string(http_response.code);
      respons_line += " ";
      respons_line += Code2Desc(http_response.code);
      Log::LOG(DEBUG,"respons_line:"+respons_line);
      respons_line += LINE_END;
      //处理状态码
      //构建header
      auto &code = http_response.code;
      switch(code)
      {
        case OK:
          BuildHttpRespondOKHeader();
        break;
        case NOT_FOND:
          BuildHttpRespondERRHeader(PAGE_404);
        break;
        case SERVER_ERR:
          BuildHttpRespondERRHeader(PAGE_500);
        default:
        break;
      }

    }

  public:
  EndPoint(int sock):_sock(sock),stop(false)
  {}
    //接收请求
    void RecvHttpRequest()
    {
      if((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader())){
        ParseHttpRequestLine();
        ParseHttpRequestHeader();
        RecvHttpRequestBody();
      }
    }
    //构造回应
    void BuildHttpRespond()
    {
      Log::LOG(INFO,"BuildHttpRespond begin");
      //request读完了,有了完整的请求。
      auto &uri_path = http_request.uri_path;
      auto &uri_message = http_request.uri_message;
      auto &file_suffix = http_request.file_suffix; 
      
      size_t suffix_index;
      
      //没有此方法处理
      if(http_request.method != "POST" && http_request.method != "GET")
      {
          http_response.code = 404;
          goto END;
      }
      if(http_request.method == "GET")
      {
        Log::LOG(DEBUG,"request_method is GET");
        //解析uri
        size_t index = http_request.uri.find("?");
        if(index == http_request.uri.npos)
        {
          http_request.uri_path = http_request.uri;
        }
        //uri携带信息,cgi设置
        else{
          Util::CutString(uri_path,uri_message,"?",http_request.uri);
          http_request.cgi = true;
        }
      }
      else if(http_request.method == "POST")
      {
        //post,设置cgi
        http_request.cgi = true;
        uri_path = http_request.uri;
      }
      else {
        //do noting
      }
      //path为/结尾
      if(uri_path[uri_path.size()-1] == '/')
      {
        uri_path += HOME_PATH;
      }
      //获取到path,拼接wwwroot
      uri_path = WWWROOT + http_request.uri_path; 
        Log::LOG(DEBUG,"uri_path:"+uri_path);
      //使用stat函数获取文件属性。
      struct stat buff;
      if(stat(uri_path.c_str(),&buff) < 0)
      {
        //文件不存在
        Log::LOG(WARNING,"no found file:"+uri_path);
        http_response.code = NOT_FOND;
        goto END;
      }
      //文件一定存在
      //文件是目录
      if(buff.st_mode & S_IFDIR)
      {
        uri_path += '/';
        uri_path += HOME_PATH;
        //重新获取文件属性
        if(stat(uri_path.c_str(),&buff) < 0)
        {
          //文件不存在
          Log::LOG(WARNING,"no found file:"+uri_path);
          http_response.code = 404;
          goto END;
        }
      }
      //文件为可执行文件
      if((buff.st_mode & S_IXUSR )| (buff.st_mode & S_IXGRP) | (buff.st_mode & S_IXOTH))
      {
        Log::LOG(INFO,"可执行文件："+uri_path);
        http_request.cgi = true;
      }
      //不为可执行文件，但cgi设为执行，直接code设为404，goto end
      else{
        if(http_request.cgi)
        {
            http_response.code = 404;
            goto END;
        }
      }
      //获取文件大小
      //可能返回文件的内容
      http_response.content_length = buff.st_size;
      //解析文件后缀
      suffix_index = uri_path.rfind(".");
      if(suffix_index == uri_path.npos)
      {
        Log::LOG(INFO,"get suffix error,maybe is bin");
        file_suffix = ".html";
      }
      else{
        file_suffix = uri_path.substr(suffix_index);
        Log::LOG(DEBUG,"file_suffix:"+file_suffix);
      }
      
      if(http_request.cgi)
      {
        http_response.code = HandlerRespondCgi();
      }
      else{
        http_response.code = HandlerRespondNonCgi();
      }
END:
      //构建报头，报文部分让SendHttpRespond处理cgi和非cgi情况。
      BuildHttpRespondHead();    
    }
    //发送回应
    void SendHttpRespond()
    {
      //响应报头全部填充完毕直接发送即可，根据是否设置cgi，选择如何发送body。
      Log::LOG(INFO,"SendHttpRespond begin");
      auto &respons_line = http_response.respons_line;
      auto &respons_header = http_response.respons_header;
      send(_sock,respons_line.c_str(),respons_line.size(),0);
      for(auto it : respons_header)
      {
        send(_sock,it.c_str(),it.size(),0);
      }
      send(_sock,http_response.blank.c_str(),http_response.blank.size(),0);
      
      if(http_request.cgi)
      {
        char* request_body = (char*)http_response.respons_body.c_str();
        size_t remain = http_response.respons_body.size(); 
        size_t begin = 0;
        size_t sz;
        while(remain > 0)
        {
          sz = send(_sock,request_body+begin,remain,0);
          if(sz < 0)
          {
            //对端关闭连接
            Log::LOG(WARNING,"send Response_CGI error");
            return;
          }
          remain -= sz;
          begin += sz;
        }
      }
      else{
          off_t offset = 0;
          if(sendfile(_sock,http_response.fd,&offset,http_response.content_length) < 0)
          {
            //对端关闭连接
            Log::LOG(WARNING,"send Response_NONCGI error");
            close(http_response.fd);
            return;
          }
        //关闭fd
        close(http_response.fd);
        Log::LOG(INFO,"SendHttpRespond end");
      }
    }
  ~EndPoint()
  {}
};

class Entrance{
public:
  Entrance()
  {
  }
  static void* HandlerRequest(void* arg)
  {
    int sock = *(int*)arg;
    delete (int*)arg;

    EndPoint e(sock);
    e.RecvHttpRequest();
    e.BuildHttpRespond();
    e.SendHttpRespond();
    return nullptr;
  }

  ~Entrance()
  {}
};
